﻿using Heroius.Extension;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.Pkcs;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using _O = Org.BouncyCastle.X509;
using Org.BouncyCastle.X509.Extension;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using _S = System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml.Linq;
using System.Security.Cryptography.X509Certificates;

namespace Heroius.Cert.BouncyCastle
{
    public class Engine: IEngine
    {

        public void Init(Dictionary<string, string> config)
        {

            BSetting = new BCSetting(config);
        }

        public GenResult Gen(CertInfo info)
        {
            entity = info;
        }

        BCSetting BSetting;

        CertInfo entity;

        TripleDESCryptoServiceProvider setsec = new TripleDESCryptoServiceProvider();

        public event Action<string> SendMessage;

        DictEvalEntity IEngine.Setting => throw new NotImplementedException();

        string Encrypt(string plain)
        {
            if (string.IsNullOrEmpty(plain)) return plain;
            setsec.Key = Encoding.Default.GetBytes(BSetting.SetSecKey);
            setsec.IV = BSetting.SetSecIV.Split(',').Select(str => Convert.ToByte(str)).ToArray();
            var block = Encoding.UTF8.GetBytes(plain);
            return Convert.ToBase64String(setsec.CreateEncryptor().TransformFinalBlock(block, 0, block.Length));
        }

        string Decrypt(string cipher)
        {
            if (string.IsNullOrEmpty(cipher)) return cipher;
            setsec.Key = Encoding.Default.GetBytes(BSetting.SetSecKey);
            setsec.IV = BSetting.SetSecIV.Split(',').Select(str => Convert.ToByte(str)).ToArray();
            var block = Convert.FromBase64String(cipher);
            return Encoding.UTF8.GetString(setsec.CreateDecryptor().TransformFinalBlock(block, 0, block.Length));
        }

        //this part of codes is inspired by GranadaCoder at https://granadacoder.wordpress.com/2016/11/04/service-bus-and-custom-self-signed-certificates-with-a-high-availabilitymultiple-computing-nodes-in-the-farm/
        public X509Certificate2 Generate(string path)
        {
            bool SelfSigned = string.IsNullOrWhiteSpace(entity.IssuerId);//标记：是否为自签名证书

            _S.X509Certificate2 issuer_cert_S = null; //签发者证书
            //_O.X509Certificate issuer_cert_O = null;
            string issuerName = null; //签发者名称
            AsymmetricKeyParameter issuerPubKey = null; //签发者公钥
            AsymmetricKeyParameter issuerPrivKey = null; //签发者私钥

            if (!SelfSigned)//非自签名
            {
                // 读取颁发者证书名和私钥
                issuer_cert_S = new _S.X509Certificate2(entity.IssuerCert, entity.IssuerPrivateKeyPassword, _S.X509KeyStorageFlags.Exportable | _S.X509KeyStorageFlags.PersistKeySet);

                issuerName = issuer_cert_S.SubjectName.Name;
                var skey = (System.Security.Cryptography.RSACryptoServiceProvider)issuer_cert_S.PrivateKey;
                AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetRsaKeyPair(skey);
                issuerPubKey = keyPair.Public;
                issuerPrivKey = keyPair.Private;
            }
            else
            {
                issuerName = entity.CertId;
            }

            // The Certificate Generator
            var certgen = new _O.X509V3CertificateGenerator();

            #region Basic
            // Generating SerialNumber using Random
            var random = new SecureRandom(new CryptoApiRandomGenerator());
            var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
            certgen.SetSerialNumber(serialNumber);

            // Signature Algorithm
            certgen.SetSignatureAlgorithm("SHA256WithRSA");

            // Issuer and Subject Name
            var subjectDN = new X509Name(entity.CertId);

            //todo: 使用openssl生成的证书中会包含 S 项（state），而bouncycastle不识别
            var issuerDN = new X509Name(issuerName);
            certgen.SetIssuerDN(issuerDN);
            certgen.SetSubjectDN(subjectDN);

            // Valid Date
            var notBefore = entity.From;
            var notAfter = entity.To;
            certgen.SetNotBefore(notBefore);
            certgen.SetNotAfter(notAfter);
            #endregion

            // usage
            //是否影响根证书可用性? 是: 根证书不应包含密钥用法属性
            if (entity.KeyUsages.Count > 0)
            {
                var u1 = entity.KeyUsages[0].As<int>();
                for (int i = 1; i < entity.KeyUsages.Count; i++)
                {
                    u1 = u1 | entity.KeyUsages[i].As<int>();
                }
                Org.BouncyCastle.Asn1.X509.KeyUsage keyUsage = new Org.BouncyCastle.Asn1.X509.KeyUsage(u1);
                certgen.AddExtension(X509Extensions.KeyUsage, true, keyUsage);
            }

            // enhanced key usage 
            //是否影响根证书可用性? 是: 根证书不应包含任何增强型用法属性
            if (entity.EnhancedKeyUsages.Count > 0)
            {
                certgen.AddExtension(X509Extensions.ExtendedKeyUsage, false, new ExtendedKeyUsage(entity.EnhancedKeyUsages.Select(eku => eku.ToKP()).ToArray()));
            }

            //restrict：是否为颁发机构证书的标记
            if (SelfSigned)
                certgen.AddExtension(X509Extensions.BasicConstraints, false, new BasicConstraints(0));
            else
                certgen.AddExtension(X509Extensions.BasicConstraints, false, new BasicConstraints(entity.CA));

            #region SANs
            //see https://www.digicert.com/subject-alternative-name.htm for more info
            if (entity.AlterNames.Count <= 1)
            {
                /* the <=1 is for the simple reason of showing an alternate syntax .. */
                foreach (var subjectAlternateName in entity.AlterNames)
                {
                    GeneralName altName = new GeneralName(GeneralName.DnsName, subjectAlternateName);
                    GeneralNames subjectAltName = new GeneralNames(altName);
                    certgen.AddExtension(X509Extensions.SubjectAlternativeName, false, subjectAltName);
                }
            }
            else
            {
                List<Asn1Encodable> asn1EncodableList = new List<Asn1Encodable>();
                foreach (var subjectAlternateName in entity.AlterNames)
                {
                    asn1EncodableList.Add(new GeneralName(GeneralName.DnsName, subjectAlternateName));
                }

                DerSequence subjectAlternativeNamesExtension = new DerSequence(asn1EncodableList.ToArray());
                certgen.AddExtension(X509Extensions.SubjectAlternativeName.Id, false, subjectAlternativeNamesExtension);
            }
            #endregion

            #region Key and Cert gen
            // Subject Public Key
            AsymmetricCipherKeyPair subjectKeyPair;
            var keyGenerationParameters = new KeyGenerationParameters(random, entity.KeyBits);
            var keyPairGenerator = new RsaKeyPairGenerator();
            keyPairGenerator.Init(keyGenerationParameters);
            subjectKeyPair = keyPairGenerator.GenerateKeyPair();
            certgen.SetPublicKey(subjectKeyPair.Public);

            // ？？
            if (!SelfSigned)
            {
                var issuer_cert_O = new _O.X509CertificateParser().ReadCertificate(issuer_cert_S.GetRawCertData());
                certgen.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(issuer_cert_O));
            }
            else
            {
                //是否影响根证书可用性? 否
                certgen.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(subjectKeyPair.Public));
            }
            //是否影响根证书可用性? 否
            certgen.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(subjectKeyPair.Public));

            // sign certificate
            if (SelfSigned) issuerPrivKey = subjectKeyPair.Private;
            var cert_O = certgen.Generate(issuerPrivKey, random);

            // merge correcponding private key into X509Certificate2
            PrivateKeyInfo pinfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(subjectKeyPair.Private);
            var cert_S = new _S.X509Certificate2(cert_O.GetEncoded());
            var seq = (Asn1Sequence)Asn1Object.FromByteArray(pinfo.PrivateKeyData.GetDerEncoded());
            if (seq.Count != 9)
            {
                throw new PemException("malformed sequence in RSA private key");
            }
            var rsa = new RsaPrivateKeyStructure(seq);
            RsaPrivateCrtKeyParameters rsaparams = new RsaPrivateCrtKeyParameters(rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient);
            cert_S.PrivateKey = DotNetUtilities.ToRSA(rsaparams);

            // file export
            File.WriteAllBytes(path.Replace(".pfx", ".cer"), cert_S.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Cert));
            File.WriteAllBytes(path, cert_S.Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, entity.PfxExportPassword));
            #endregion
            return cert_S;
        }

    }
}
